package com.lanxiao.cloud.common.web.aop;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.lanxiao.cloud.common.core.dto.ArgDto;
import com.lanxiao.cloud.common.core.dto.UserAgentDto;
import com.lanxiao.cloud.common.core.enums.OperationType;
import com.lanxiao.cloud.common.core.enums.StateEnum;
import com.lanxiao.cloud.common.core.service.IOperatorService;
import com.lanxiao.cloud.common.web.SpringUtil;
import com.lanxiao.cloud.common.web.bus.event.LogEvent;
import com.lanxiao.cloud.common.web.bus.event.LogEventPublisher;
import com.lanxiao.cloud.common.web.invalid.Log;
import com.lanxiao.cloud.common.web.invalid.LogDataName;
import com.lanxiao.cloud.common.web.service.IContentParse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.HandlerMapping;

/**
 * @author ：qiaoliang
 * @date ：2021-08-19
 */
@Aspect
public class LogAspect {

  private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

  private final LogEventPublisher logEventPublisher;
  private final IOperatorService operatorService;
  @Value("${spring.application.name}")
  private String applicationName;

  public LogAspect(LogEventPublisher logEventPublisher,
      IOperatorService operatorService) {
    this.logEventPublisher = logEventPublisher;
    this.operatorService = operatorService;
  }

  @Around("@annotation(logs)")
  public Object saveSysLog(ProceedingJoinPoint proceedingJoinPoint, Log logs) throws Throwable  {
    //获取开始时间
    Stopwatch stopwatch = Stopwatch.createStarted();
    LogEvent<Long> logEvent = new LogEvent<>(applicationName);
    Object resultObject = null;
    try {

      this.fillMethodParameter(logEvent, proceedingJoinPoint);
      this.fillUserAgent(logEvent);
      logEvent.setType(logs.operationType());

      Map<String, Object> oldMap = new HashMap<>(10);
      if(OperationType.UPDATE.equals(logs.operationType())) {
        oldMap = BeanUtil.beanToMap(this.getResult(logEvent.getServiceId(), logs));
      }

      //请求返回数据
      resultObject = proceedingJoinPoint.proceed();

      if(OperationType.UPDATE.equals(logs.operationType())) {
        final Object result = this.getResult(logEvent.getServiceId(), logs);
        logEvent.setContent(this.defaultDealUpdate(result, oldMap));
      }

    } catch (Throwable e){
      logEvent.setState(StateEnum.ERROR);
      logEvent.setErrorMessage(e.getMessage());
      throw e;
    } finally {
      stopwatch.stop();
      logEvent.setTimeConsuming(stopwatch.elapsed(TimeUnit.MILLISECONDS));
      logEvent.setResponseParams(resultObject);
      logEventPublisher.asyncPublish(logEvent);
      log.info(proceedingJoinPoint.getSignature().getName() + "方法日志：{}", JSONUtil.toJsonStr(logEvent));
    }
    return resultObject;
  }


  /**
   * 填充用户信息
   *
   */
  private void fillUserAgent(LogEvent<Long> logEvent) {
    final UserAgentDto<Long> userAgent = operatorService.getUserAgent();
    logEvent.setIp(userAgent.getIp());
    logEvent.setBrowser(userAgent.getBrowser());
    logEvent.setRegion(userAgent.getCityName());
    logEvent.setOperatingSystem(userAgent.getOperatingSystem());
    logEvent.setCreateBy(userAgent.getOperator());
  }

  /**
   * 填充方法，类，描述信息
   *
   */
  private void fillMethodParameter(LogEvent<Long> logEvent, ProceedingJoinPoint proceedingJoinPoint){
    final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    if(Objects.nonNull(requestAttributes)) {
      HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
      Map attribute =  (Map)request.getAttribute(
          HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
      logEvent.setServiceId(attribute.get("id"));
      // 请求路径
      logEvent.setUrl(request.getRequestURL().toString());
    }

    // 获取请求参数进行打印
    Signature signature = proceedingJoinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    //得到目标方法
    Method method = methodSignature.getMethod();

    // 类名
    // swagger中文注释名
    String classCommentName = method.getDeclaringClass().getAnnotation(Api.class).tags()[0];
    String[] sourceName = signature.getDeclaringTypeName().split("\\.");
    logEvent.setServiceType(classCommentName);
    logEvent.setClassName(sourceName[sourceName.length - 1]);

    // 方法名
    // swagger中文注释名
    String methodCommentName = method.getAnnotation(ApiOperation.class).value();
    logEvent.setMethodName(signature.getName());
    logEvent.setMethodDescription(methodCommentName);

    // 参数名数组
    String[] parameterNames = ((MethodSignature) signature).getParameterNames();
    Map<String, Object> map = Maps.newHashMap();
    if (Objects.nonNull(parameterNames)) {
      final Object[] args = proceedingJoinPoint.getArgs();
      for (int i = 0; i < parameterNames.length; i++) {
        map.put(parameterNames[i], args[i]);
      }
    }
    logEvent.setRequestParams(map);
  }

  private Object getResult(Object id, Log logs) {
    final IContentParse contentParse = SpringUtil.getBean(logs.parseClass());
    return contentParse.getResult(id, logs);
  }

  private List<ArgDto> defaultDealUpdate(Object newObject, Map<String, Object> oldMap){
    List<ArgDto> list = Lists.newArrayList();
    if(Objects.isNull(newObject) || CollUtil.isNotEmpty(oldMap)) {
      return null;
    }
    try {
      Map<String, Object> newMap = BeanUtil.beanToMap(newObject);
      oldMap.forEach((k, v) -> {
        Object newResult = newMap.get(k);
        if (Objects.nonNull(v) && !v.equals(newResult)) {
          Field field = BeanUtil.getBeanDesc(newObject.getClass()).getField(k);
          if(Objects.nonNull(field)) {
            LogDataName dataName = field.getAnnotation(LogDataName.class);
            if (Objects.nonNull(dataName)) {
              list.add(new ArgDto(field.getName(), dataName.name(), v, newResult));
            }else {
              list.add(new ArgDto(field.getName(), null , v, newResult));
            }
          }
        }
      });
      return list;
    } catch (Exception e) {
      log.error("比较异常", e);
    }
    return list;
  }

}
